package com.turbo.cloud.core.security;

import com.turbo.cloud.core.security.authentication.AdminUserAccessDeniedHandler;
import com.turbo.cloud.core.security.authentication.AdminUserAuthenticationEntryPoint;
import com.turbo.cloud.core.security.authentication.JwtAuthTokenFilter;
import com.turbo.cloud.core.security.config.IgnoreUrlsConfig;
import com.turbo.cloud.core.security.jwt.JwtProperties;
import com.turbo.cloud.core.security.config.AnonymousUrlsConfig;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


/**
 * SpringSecurity的配置
 * prePostEnabled = true 的作用的是启用Spring Security的@PreAuthorize 以及@PostAuthorize 注解。
 * securedEnabled = true 的作用是启用Spring Security的@Secured 注解。
 * jsr250Enabled = true 的作用是启用@RoleAllowed 注解
 *
 * @author zhangluning
 */
@Order(100)
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    private final JwtProperties jwtProperties;
    private final IgnoreUrlsConfig ignoreUrlsConfig;
    private final AdminUserAccessDeniedHandler accessDeniedHandler;
    private final AdminUserAuthenticationEntryPoint authenticationEntryPoint;
    private final AnonymousUrlsConfig anonymousUrlsConfig;

    /**
     * 静态资源
     */
    private static final String[] AUTH_STATIC_URLS = {"/", "/*.html", "/favicon.ico", "/*/*.html", "/*/*.css", "/*/*.js"};
    /**
     * 接口资源
     */
    private static final String[] AUTH_API_URLS = {"/oauth/*", "/admin/**", "/interface/**"};

    /**
     * 匿名接口资源
     */
    private static String[] AUTH_ANONYMOUS_API_URLS = {};

    /**
     * 于配置需要拦截的url路径、jwt过滤器及出异常后的处理器；
     */
    @Bean
    SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        // 注解标记允许匿名访问的url
        AUTH_ANONYMOUS_API_URLS = anonymousUrlsConfig.getUrls().toArray(new String[0]);

        httpSecurity
                // 由于使用的是JWT，我们这里不需要csrf
                .csrf(CsrfConfigurer::disable)
                // 基于token，所以不需要session
                .sessionManagement(SessionManagementConfigurer :: disable)
                // 所有的请求都验证
                .authorizeHttpRequests(authorize->authorize.requestMatchers(AUTH_STATIC_URLS).permitAll()
                        .requestMatchers(AUTH_API_URLS).permitAll()
                        .requestMatchers(AUTH_ANONYMOUS_API_URLS).permitAll()
                        .requestMatchers(HttpMethod.OPTIONS).permitAll()
                        .anyRequest().authenticated());

        // 禁用缓存
        httpSecurity.headers(header -> header.cacheControl(HeadersConfigurer.CacheControlConfig::disable));
        // 添加JWT filter
        httpSecurity.addFilterBefore(new JwtAuthTokenFilter(jwtProperties), UsernamePasswordAuthenticationFilter.class);
        //添加自定义未授权和未登录结果返回
        httpSecurity.exceptionHandling(exception -> {
            exception.accessDeniedHandler(accessDeniedHandler);
            exception.authenticationEntryPoint(authenticationEntryPoint);
        });

        return httpSecurity.build();
    }

    /**
     * 密码加密方式配置
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
        //这里使用国密
        //return new Sm4PasswordEncoder();
    }
}
